/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
 
package org.erossi.common.model;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.LockMode;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;

/**
 * 'GenericHbDAO' class: classe astratta che rappresenta il DAO generico orientato ad Hibernate 
 * 
 * <p>
 *   La classe utilizza i seguenti pattern:
 *   <ul>
 *      <li>Data Access Object <a href="http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html">http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html</a>
 *   </ul>     
 * </p> 
 * <p>
 *   L'accesso ai dati viene garantito attraverso l'uso delle <b>contextual sessions</b> di Hibernate 3.
 * </p>
 *
 * @author erossi
 * @version Feb 9, 2007
 */
public abstract class GenericHbDAO<T, ID extends Serializable> implements GenericDAO<T, ID> {

  private Class<T> persistentClass;

  private SessionFactory sessionFactory;
 
  /**
   * Crea una nuova istanza della classe
   * 
   * <p>
   *   Nel costruttore viene popolato l'attributo {@link #persistentClass}, ovvero la classe
   *   per la cui persistenza viene utilizzato il DAO generico che la classe corrente rappresenta.
   * </p> 
   * <p>
   *   Per fare questo viene utilizzata la reflection, basandosi sul fatto che, nell'uso tipico, il costruttore 
   *   corrente viene invocato a partire da una sua sottoclasse diretta.
   * </p>
   * <p>
   *   Esiste però almeno un caso significativo in cui ciò non accade, ovvero nel caso in cui, attraverso l'uso 
   *   delle tecniche di Aspect Oriented Programming (AOP), la sottoclasse diretta viene a sua volta estesa,
   *   per implementare l'AOP Proxy.
   * </p>
   * <p>
   *   Tale circostanza viene qui gestita, ovvero nel caso in cui il costruttore corrente venga invocato da una 
   *   sottoclasse di secondo grado, il costruttore si preoccupa di risalire di un livello nella gerarchia 
   *   delle classi fino alla classe corretta.
   * </p>
   * <p>
   *   <b>Ulteriori livelli di sub-classing non sono gestiti e, verosimilmente, genereranno eccezioni di tipo
   *   {@link java.lang.ClassCastException} nel popolamento dell'attributo {@link #persistentClass}</b>
   * </p>
   */
  @SuppressWarnings("unchecked")
  public GenericHbDAO() {

    Class realDAOClass = null;
    Class invokingClass = this.getClass();
    
    Class superClass = invokingClass.getSuperclass();
    if (superClass != GenericHbDAO.class) {
      realDAOClass = invokingClass.getSuperclass();
    } else {
      realDAOClass = invokingClass;
    }
      
    this.persistentClass = (Class<T>) ((ParameterizedType) realDAOClass.getGenericSuperclass()).getActualTypeArguments()[0];
  
  }
  
  /**
   * Imposta il valore dell'attributo 'sessionFactory'
   * @param sessionFactory il valore dell'attributo 'sessionFactory'
   */
  public void setSessionFactory(SessionFactory sessionFactory) {
    this.sessionFactory = sessionFactory;
  }

  /**
   * @see org.erossi.common.model.GenericDAO#cercaPerId(Serializable, boolean)
   */
  @SuppressWarnings("unchecked")
  public T cercaPerId(ID id, boolean lock) {

    T entity;

    if (lock) {
      entity = (T) this.sessionFactory.getCurrentSession().load(persistentClass, id, LockMode.UPGRADE);
    } else {
      entity = (T) this.sessionFactory.getCurrentSession().load(persistentClass, id, LockMode.READ);
    }

    return entity;

  }

  /**
   * @see org.erossi.common.model.GenericDAO#cercaTutti()
   */
  public List<T> cercaTutti() {
    return this.cercaPerFiltro();
  }

  /**
   * @see org.erossi.common.model.GenericDAO#cercaPerEsempio(Object, String[])
   */
  @SuppressWarnings("unchecked")
  public List<T> cercaPerEsempio(T esempio, String[] attributiEsclusi) {
    
    Criteria crit = this.sessionFactory.getCurrentSession().createCriteria(persistentClass);

    Example example = Example.create(esempio);

    for (String attributo : attributiEsclusi) {
      example.excludeProperty(attributo);
    }

    crit.add(example);

    return crit.list();

  }

  /**
   * @see org.erossi.common.model.GenericDAO#registra(Object)
   */
  public T registra(T entity) {

    this.sessionFactory.getCurrentSession().saveOrUpdate(entity);
    return entity;

  }

  /**
   * @see org.erossi.common.model.GenericDAO#cancella(Object)
   */
  public void cancella(T entity) {
    this.sessionFactory.getCurrentSession().delete(entity);
  }

  /**
   * Esegue il flush della sessione Hibernate.
   * 
   * @see org.hibernate.Session#flush()
   */
  public void flush() {
    this.sessionFactory.getCurrentSession().flush();
  }

  /**
   * Esegue il clear della sessione Hibernate.
   * 
   * @see org.hibernate.Session#clear()
   */
  public void clear() {
    this.sessionFactory.getCurrentSession().clear();
  }

  /**
   * Metodo da utilizzare nelle sottoclassi come metodo di utilità generico
   *   per la creazione di ricerche basate su criteri arbitrari e variabili.
   *    
   * @param criteri elenco <code>varargs</code>
   * @return lista dei bean che rispondono ai criteri
   */
  @SuppressWarnings("unchecked")
  protected List<T> cercaPerFiltro(Criterion... criteri) {
    
    Criteria crit = this.sessionFactory.getCurrentSession().createCriteria(persistentClass);
    
    for (Criterion c : criteri) {
      crit.add(c);
    }

    return crit.list();

  }

}

